package pl.enigmatic.parser.string;

import java.util.HashMap;

import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

import pl.enigmatic.ValueConverter;
import pl.enigmatic.exception.WrongArgumentsException;
import pl.enigmatic.util.Reflection;
import pl.enigmatic.util.Strings;
import pl.enigmatic.parser.XMLConverter;


public abstract class Converter<T> implements ValueConverter<String, T> {

	static {
		DOMConfigurator.configure("log4j.xml");
	}

	private static final Logger logger = Logger.getLogger(Converter.class);

	public static final String getDefaultConverterName(final Class<?> clazz) {
		if (clazz.isAnnotationPresent(XMLConverter.class)) { return clazz.getAnnotation(XMLConverter.class).value().getName(); }
		if (clazz.isEnum()) {
			return EnumConverter.class.getName();
		} else if (clazz.isArray()) {
			return ArrayConverter.class.getName();
		} else {
			return Converter.class.getPackage().getName() + "." + Reflection.wrapper(clazz).getName() + Converter.class.getSimpleName();
		}
	}

	private static final String getDeaultConverterID(final Class<?> clazz) {
		if (clazz.isEnum() || clazz.isArray()) {
			return clazz.getName();
		} else {
			return getDefaultConverterName(clazz);
		}
	}

	private final static HashMap<String, Converter<?>> converters = new HashMap<String, Converter<?>>();

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static final <T> Converter<T> createDefaultConverter(final Class<T> clazz) {
		if (clazz == null) { return null; }
		final String id = getDeaultConverterID(clazz);
		if (!converters.containsKey(id)) {
			if (clazz.isEnum()) {
				converters.put(id, new EnumConverter<T>(clazz));
			} else if (clazz.isArray()) {
				if (Reflection.arrayDepth(clazz) > ArrayConverter.MAX_DEPTH) {
					logger.error(String.format("%1$s is not available for type %2$s, the array depth is too big, should be at most 2.",
							Converter.class.getName(), Reflection.arrayBase(clazz).getPackage().getName() + "." + clazz.getSimpleName()),
							new WrongArgumentsException());
				} else {
					if (Reflection.isStrictPrimitive(Reflection.arrayBase(clazz))) {
						converters.put(id, new PrimitiveArrayConverter(clazz));
					} else {
						converters.put(id, new ArrayConverter(clazz));
					}
				}
			} else {
				converters.put(id, (Converter<?>) Reflection.newInstance(getDefaultConverterName(clazz)));
			}
		}
		return (Converter<T>) converters.get(id);
	}

	@SuppressWarnings({ "unchecked" })
	public static final <T> Converter<T> createDefaultConverter(final String clazz) {
		final Class<T> c = (Class<T>) Reflection.classForName(clazz);
		final String id = getDeaultConverterID(c);
		if (!converters.containsKey(id)) {
			return createDefaultConverter(c);
		} else {
			return (Converter<T>) converters.get(id);
		}
	}

	public static final Converter<?> createDefaultArrayConverter(final Class<?> clazz, final int depth) {
		return createDefaultConverter(Reflection.arrayType(clazz, depth));
	}

	public static final Converter<?> createDefaultArrayConverter(final String clazz, final int depth) {
		return createDefaultConverter(Reflection.arrayType(Reflection.classForName(clazz), depth));
	}

	public static final Converter<?> create(final String clazz) {
		if (!converters.containsKey(clazz)) {
			final Converter<?> res = Reflection.newInstance(clazz);
			converters.put(clazz, res);
			return res;
		} else {
			return converters.get(clazz);
		}
	}

	public static final <C extends Converter<?>> Converter<?> create(final Class<C> clazz) {
		return create(clazz.getName());
	}

	public static abstract class Primitive<T> extends Converter<T> {

		@Override
		public T convert(final String value) {
			if (Strings.empty(value)) {
				return convertEmptyString();
			} else {
				return convertNonEmptyString(value);
			}
		}

		@Override
		public String convertBack(final T value) {
			if (value == null) {
				return "";
			} else {
				return value.toString();
			}
		}

		protected abstract T convertEmptyString();

		protected abstract T convertNonEmptyString(String value);
	}

}
